﻿namespace RLab.Programming

open System
open Microsoft.FSharp.Text.Lexing
open Ast


module Evaluator = 
    let Evaluate txt programmable (observer : IObserver<string>)=
        let event = new Event<string>()
        let unsubsrciber = event.Publish.Subscribe(observer)
        try        
            let evaluateAst (programmable : IProgrammable) program  = 
                let rec evalE (env: Map<string, double>) = function
                    | Id v -> if env.ContainsKey v then env.[v]
                                else failwith ("unbound variable: " + v)
                    | Int i -> (double)i
                    | Float i -> i
                    | Add (e1, e2) -> evalE env e1 + evalE env e2
                    | Sub (e1, e2) -> evalE env e1 - evalE env e2
                    | Mul (e1, e2) -> evalE env e1 * evalE env e2
                    | Div (e1, e2) -> evalE env e1 / evalE env e2

                and eval (env: Map<string, double>) = function
                    | Assign (v, e) ->
                        env.Add(v, evalE env e)
                    | While (e, body) ->
                        let rec loop env e body =
                            if evalE env e <> 0.0 then
                                loop (eval env body) e body
                            else env
                        loop env e body
                    | IfThen (e, stmt) ->
                        if evalE env e <> 0.0 then eval env stmt else env
                    | IfThenElse (e, stmt1, stmt2) ->
                        if evalE env e <> 0.0 then eval env stmt1 else eval env stmt2
                    | Drive (i,f) -> programmable.Drive ((int)(evalE env i)) ((float32)(evalE env f)); env
                    | Base -> programmable.Base(); env
                    | Seq (list) -> List.fold eval env list 
                       
                match program with
                | Prog(a,b) -> eval Map.empty (Seq(b)) |> ignore

            let parseText text = 
                let lexbuf = LexBuffer<_>.FromString  text
                try
                    Parser.Start Lexer.tokenize lexbuf
                with e->
                    let pos = lexbuf.EndPos
                    failwithf "Error syntax near line %d" pos.Line                   

            evaluateAst programmable (parseText txt )
        with e->
            event.Trigger(e.Message)
    let EvaluateAsync txt programmable observer =
        let f = async{ Evaluate txt programmable observer}
        Async.Start f